/*
 * Created on 29.11.2003
 */
package de.javakicker;

import java.io.Writer;
import java.io.IOException;
import java.util.Stack;

/**
 * Ein einfacher XML-Writer, der XML-Daten sequentiell in einen Ausgabestrom
 * schreibt.
 * 
 * @author Till Krullmann
 */

public class XmlWriter
{
	
	/** The writer. */
	private Writer writer;
	
	/** The tag names. */
	private Stack<String> tagNames = new Stack<String>();
	
	/** The current element empty. */
	private boolean currentElementEmpty = true;
	
	/** The attribute open. */
	private boolean attributeOpen = false;
	
	/** The do indent. */
	private boolean doIndent = false;
	
	/** The indent level. */
	private int indentLevel = 0;
	
	
	/**
	 * Instantiates a new xml writer.
	 *
	 * @param writer the writer
	 */
	public XmlWriter(Writer writer)
	{
		this(writer, false);
	}
	
	
	/**
	 * Instantiates a new xml writer.
	 *
	 * @param writer the writer
	 * @param doIndent the do indent
	 */
	public XmlWriter(Writer writer, boolean doIndent)
	{
		if (writer == null)
			throw new IllegalArgumentException("Internal writer cannot be null.");
		this.writer = writer;
		this.doIndent = doIndent;
	}
	
	
	/**
	 * Write raw.
	 *
	 * @param str the str
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeRaw(String str)
		throws IOException
	{
		writer.write(str);
	}
	
	
	/**
	 * Close start tag.
	 *
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void closeStartTag()
		throws IOException
	{
		if (!tagNames.empty() && currentElementEmpty)
		{
			writer.write(">");
			if (doIndent)
				indentLevel++;
		}

		if (doIndent)
		{	
			writer.write("\n");
			for (int i = 0; i < indentLevel; i++)
				writer.write("\t");
		}
	}
	
	
	/**
	 * Write start element.
	 *
	 * @param tagName the tag name
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeStartElement(String tagName)
		throws IOException
	{
		closeStartTag();
		
		writer.write("<" + tagName);
		tagNames.push(tagName);
		
		currentElementEmpty = true;
	}
	
	
	/**
	 * Write end element.
	 *
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeEndElement()
		throws IOException
	{
		if (tagNames.empty())
			throw new IllegalStateException("There are no open elements to close.");
		
		String tagName = (String) tagNames.pop();
		
		if (currentElementEmpty)
			writer.write(" />");
		else
		{
			if (doIndent)
			{
				indentLevel--;
				writer.write("\n");
				for (int i = 0; i < indentLevel; i++)
					writer.write("\t");
			}
			writer.write("</" + tagName + ">");
		}
			
		currentElementEmpty = false;
	}
	
	
	/**
	 * Write element string.
	 *
	 * @param tagName the tag name
	 * @param value the value
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeElementString(String tagName, String value)
		throws IOException
	{
		writeStartElement(tagName);
		writeString(value);
		writeEndElement();
	}
	
	
	/**
	 * Write start attribute.
	 *
	 * @param name the name
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeStartAttribute(String name)
		throws IOException
	{
		if (tagNames.empty())
			throw new IllegalStateException("You must start an element before you can add attributes.");
		if (!currentElementEmpty)
			throw new IllegalStateException("You must write all attributes before adding child elements.");
		
		if (attributeOpen)
			writeEndAttribute();
		
		writer.write(" " + name + "=\"");
		
		attributeOpen = true;
	}
	
	
	/**
	 * Write end attribute.
	 *
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeEndAttribute()
		throws IOException
	{
		writer.write("\"");
		attributeOpen = false;
	}
	
	/**
	 * Schreibt ein beliebiges Attribut in den aktuellen Tag.
	 *
	 * @param name Name des Attributs
	 * @param value Wert des Attributs
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeAttributeString(String name, String value)
		throws IOException
	{
		writeStartAttribute(name);
		writeString(value);
		writeEndAttribute();
	}
	
	
	/**
	 * Write string.
	 *
	 * @param text the text
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeString(String text)
		throws IOException
	{
		if (text.length() == 0)
			return;
		
		String str = new String(text);
		
		str.replaceAll("&", "&amp;");
		str.replaceAll("<", "&lt;");
		str.replaceAll(">", "&gt;");
		
		if (attributeOpen)
			str.replaceAll("\"", "&quot;");
		else if (currentElementEmpty)
			closeStartTag();
		
		writer.write(str);
		
		if (currentElementEmpty && !attributeOpen)
			currentElementEmpty = false;
	}
	
	
	/**
	 * Write processing instruction.
	 *
	 * @param name the name
	 * @param text the text
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeProcessingInstruction(String name, String text)
		throws IOException
	{
		writer.write("<?" + name + " " + text + "?>\n");
	}
	
	
	/**
	 * Write comment.
	 *
	 * @param text the text
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeComment(String text)
		throws IOException
	{
		writer.write("<!--" + text + "-->");
	}
	
	
	/**
	 * Write c data.
	 *
	 * @param text the text
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeCData(String text)
		throws IOException
	{
		writer.write("<![CDATA[" + text + "]]>");
	}
	
	
	/**
	 * Write start document.
	 *
	 * @param standalone the standalone
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeStartDocument(boolean standalone)
		throws IOException
	{
		String standaloneStr = "standalone=\"" +
			(standalone ? "yes" : "no") + "\"";
		writeProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\" " + standaloneStr);
	}
	
	
	/**
	 * Write start document.
	 *
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeStartDocument()
		throws IOException
	{
		writeProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
	}
	
	
	/**
	 * Write end document.
	 *
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void writeEndDocument()
		throws IOException
	{
		while (!tagNames.empty())
			writeEndElement();
	}
	
	
	/**
	 * Flush.
	 *
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void flush()
		throws IOException
	{
		writer.flush();
	}
	
	
	/**
	 * Close.
	 *
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void close()
		throws IOException
	{
		writer.close();
	}

}
